#include <stdlib.h>
#include <pthread.h>
#include <errno.h>

#include "basics.h"
#include "options.h"
#include "fatal.h"
#include "allocate.h"
#include "simcore.h"
#include "config.h"
#include "createthr.h"
#include "xicache.h"
#include "barrier.h"
#include "callback.h"
#include "breakpoint.h"

/*
 * This file contains the control and execution infrastructure for the
 * simulator.
 *
 * Legion is multi-threaded to achieve high performance including a single
 * control thread and along with a number of exec-threads.  The exact
 * number of exec-threads being determined by the -t command line parameter
 * or the number of simulated CPUs.
 */

	/*
	 * List of all the scheduler threads the simulator uses
	 * for execution.
	 */

LIST_DEF(exec_thread_list, exec_thread_t);
static int nexec_threads;

simcpu_list_t simcpu_list;

barrier_t stop_barrier;
barrier_t run_barrier;
barrier_busy_t sync_busy_barrier;

volatile bool_t running = false;

simcpu_t *sim_cpu_alloc(
		config_proc_t *cfp,
		void *specificp)
{
	simcpu_t *sp;
	int gid;


	gid = simcpu_list.count;
	sp = LIST_ADD(simcpu_list, simcpu_t);
	sp->gid = gid;

	sp->state_bits = 0;
	sp->runnable = true;

	sp->attention = true;
	sp->async_event = true;
	sp->sync_pending = false;

#if ERROR_INJECTION
	sp->error_enabled = false;
	sp->error_check = false;
	sp->error_priv = NULL;
	sp->errorp = Xcalloc(1, error_t);
	sp->errorp->errlistp = NULL;
#endif

	sp->nextp = (simcpu_t *)0;

	 /* Pointer to the exec_thread this simcpu is on */
	sp->etp = (exec_thread_t *)0;

	/* Head of the list of simcpus for each exec_thread */
	sp->headp = (simcpu_t *)0;

	sp->decodemep = NULL;
	sp->xic_miss = NULL;
	sp->xic_miss_addrp = NULL;
	sp->config_procp = cfp;
	sp->specificp = specificp;

#if OLDPIC	/* { */
		/* per cpu performance counter */
	sp->pic0 = 0;
	sp->pic1 = 0;
	sp->pcr = 0LL;
	sp->pic_cycle = 0LL;
	sp->pic_target = 0LL;
#endif		/* } */


#if ERROR_TRAP_GEN /* { */
	sp->error_cycle = UINT64_MAX;
	sp->error_pending = false;
	sp->error_cycle_reached = false;
	sp->eep = NULL;
#endif /* ERROR_TRAP_GEN } */

	sp->cycle = 0LL;
	cycle_target_off(sp);
	sp->total_instr = 0LL;
	sp->quantum_target = 0LL;

	/*
	 * More misc stuff ..
	 * flush the xdcache, but the support functions
	 * need to be supplied by the caller
	 */
	xdcache_flush(sp);
	sp->xdc.miss = NULL;

	/*
	 * clear the breakpoint cache for this cpu
	 */
	sp->bp_infop = NULL;

	return (sp);
}

static void *exec_thread_init(void *argp)
{
	exec_thread_t *etp = (exec_thread_t *)argp;
	extern void exec_loop(exec_thread_t *etp);

	/* wait until we start running */

	barrier_wait(&run_barrier);


	/*
	 * All threads are now running, including the ctrl-thread.
	 *
	 * We call into exec_loop(), passing it the first simcpu in
	 * the list to start the execution. The exec_loop will take
	 * care of switching all simcpus in it's list to ensure that
	 * they all make equal progress.
	 */
	DBGEXECLOOP(lprintf(etp->id,
			"exec_thread%d calling exec_loop for simcpu%d \n",
			etp->id, etp->allp->gid););

	ASSERT(etp->allp != NULL);
	exec_loop(etp);

	/* We never return to here */
	return ((void*)0);
}

static void exec_thread_alloc(void)
{
	exec_thread_t 	*etp;
	pthread_t 	id;
	int 		i, n, r, etidx;
	simcpu_t	*prev_sp = NULL;
	static int	simidx = 0;

	etidx = exec_thread_list.count;
	etp = LIST_ADD(exec_thread_list, exec_thread_t);
	etp->id = etidx;

	/*
	 * We need to figure out how many simcpus this
	 * etp needs to have assigned.
	 */
	n = simcpu_list.count / nexec_threads;
	r = simcpu_list.count % nexec_threads;

	if (etp->id < r)
		n += 1;

	/* create a liked list of simcpus for each exec_thread */
	for (i = 0; i < n; i++) {
		if (simidx < simcpu_list.count) {
			simcpu_t *sp = LIST_ENTRY(simcpu_list, simidx);

			/*
			 * Save pointer of first simcpu (head) into etp
			 */
			if (etp->allp == NULL)
				etp->allp = sp;

			/*
			 * Update the nextp in the previous simcpu of this
			 * list
			 */
			if (prev_sp != NULL)
				prev_sp->nextp = sp;

			/* so all simcpus can find the head of the list */
			sp->headp = etp->allp;

			/* so all simcpus can find the exec_thread */
			sp->etp = etp;

			prev_sp = sp;
			etp->nsimcpus++;
			simidx++;
		} else {
			printf("\nERROR:: exec_thread_alloc: "
			    "simidx < simcpu_list.count (0x%x < 0x%x)",
			    simidx, simcpu_list.count);
			fatal("DIE");
		}
	}

		/* create the execution thread */
	create_thread(exec_thread_init, (void *)etp, &id);
}

static void *ctrl_thread_main(void *argp)
{
	simcpu_t *sp;
	int i, j;

	for (;;) {
		while (!running) {
			/*
			 * spin until it's time to go.  this is useful when
			 * we're not auto-starting.
			 */
		}

		/* pre-exec setup */
		for (i = 0; i < simcpu_list.count; i++) {
			sp = LIST_ENTRY(simcpu_list, i);
			sp->config_procp->proc_typep->exec_setup(sp);
		}

		/*
		 * after the run barrier everyone, including the
		 * exec-threads are running
		 */

		barrier_wait(&run_barrier);

		/*
		 * now that everybody's running, now wait for everyone to
		 * stop again
		 */

		barrier_wait(&stop_barrier);

		/* we've come to a "stop" */

		ASSERT(!running);

		/*
		 * the exec-threads should be waiting (at the run_barrier)
		 * until we start running again.  meanwhile process the
		 * stopped callbacks
		 */

		/* post-exec cleanup */
		for (i = 0; i < simcpu_list.count; i++) {
			sp = LIST_ENTRY(simcpu_list, i);
			sp->config_procp->proc_typep->exec_cleanup(sp);
		}

		/* we're stopped, call stop callbacks */
		callback_fire(CB_Stop);

		/* if we've hit a breakpoint, call breakpoint callbacks */
		if (breakpoint_any_reached()) {
			callback_fire(CB_Breakpoint);
		}
	}
}

static void ctrl_thread_alloc(void)
{
	pthread_t id;

	create_thread(ctrl_thread_main, NULL, &id);
}

/*
 * support functions for finding devices etc
 */
config_addr_t *
find_domain_address(domain_t *domainp, tpaddr_t pa)
{
	config_addr_t	*addrp;

	for (addrp = domainp->address.listp;
	     addrp != NULL && addrp->baseaddr <= pa; addrp = addrp->nextp) {
		if ((addrp->baseaddr <= pa) && (addrp->topaddr > pa)) {
			return (addrp);	/* bingo */
		}
	}
	return (NULL);
}


/*
 * The function below should never be called because the cycle_target
 * it relates to should never match the execution counter
 * If such a match occurs, we ensure an error is reported and the
 * simulator stops.
 */
void
simcore_cycle_match_error(simcpu_t *sp)
{
	fatal("Illegal cycle match error");
}

void
cycle_target_off(simcpu_t *sp)
{
	/* cant ever match zero 'cos match test after 1st increment */
	sp->cycle_target = 0LL;

	sp->cycle_target_match = simcore_cycle_match_error;
}


#if !defined(NDEBUG)	/* { */

	/*
	 * Used in various control interfaces around the simulator to
	 * modify the debug modes
	 */
void simcore_update_debug_bits(uint64_t newval)
{
	int i;

	if (debug_bits == newval) {
		return;
	}

	lprintf(-1, "debug_bits 0x%llx -> 0x%llx\n", debug_bits, newval);

	debug_bits = newval;

	for (i = 0; i < simcpu_list.count; i++) {
		simcpu_t *fsp = LIST_ENTRY(simcpu_list, i);

		if (fsp != NULL)
			xicache_instn_flush(fsp);
	}
}
#endif		/* } */

void init_threads(void)
{
	int i;

	nexec_threads = (options.threads > simcpu_list.count)
		? simcpu_list.count : options.threads;
	PRINTF(("Using %d execution thread(s)\n", nexec_threads));

	barrier_init(&stop_barrier, nexec_threads + 1);
	barrier_init(&run_barrier, nexec_threads + 1);
	barrier_busy_init(&sync_busy_barrier, nexec_threads);

	ctrl_thread_alloc();

	for (i = 0; i < nexec_threads; i++) {
		exec_thread_alloc();
	}
}

void simcore_start(void)
{
	running = true;
}

void simcore_stop(void)
{
	if (running) {
		int i;

		running = false;

		for (i = 0; i < simcpu_list.count; i++) {
			simcpu_t *sp = LIST_ENTRY(simcpu_list, i);

			sp->sync_pending = true;
			set_attention(sp);
		}
	}
}

static void simcore_cpu_state_updated(simcpu_t *sp)
{
	if ((sp->state_bits & STATE_DISABLED) ||
	    (sp->state_bits & STATE_PARKED)) {
		sp->runnable = false;

		sp->sync_pending = true;
		set_attention(sp);
	} else {
		sp->runnable = true;
	}
}

void simcore_cpu_enable(simcpu_t *sp)
{
	sp->state_bits &= ~(STATE_DISABLED);

	simcore_cpu_state_updated(sp);
}

void simcore_cpu_state_unpark(simcpu_t *sp)
{
	sp->state_bits &= ~(STATE_PARKED);

	simcore_cpu_state_updated(sp);
}

void simcore_cpu_disable(simcpu_t *sp)
{
	sp->state_bits |= STATE_DISABLED;

	simcore_cpu_state_updated(sp);
}

void simcore_cpu_state_park(simcpu_t *sp)
{
	sp->state_bits |= STATE_PARKED;

	simcore_cpu_state_updated(sp);
}
